/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/graphics/dx11.1/details/common.hpp>
#include <baja/graphics/dx11.1/graphics_fwd.hpp>
#include <baja/graphics/dx11.1/details/enum_mappings.hpp>
#include <baja/graphics/dx11.1/details/util.hpp>

namespace baja { namespace graphics { namespace dx11_1 {

namespace d = baja::graphics::dx11_1::details;

// vertex_declaration implementation
class vertex_declaration : public ivertex_declaration, public b::iunknown_provider
{
private:
    WRL::ComPtr<ID3D11Device> _d3d11Device;
    WRL::ComPtr<ID3D11InputLayout> _inputLayout;
    std::vector<D3D11_INPUT_ELEMENT_DESC> _definition;
    uint32 _total;
    uint32 _current;

public:
    vertex_declaration(ID3D11Device* d3d11Device)
    {
        BAJA_VERIFY(d3d11Device != NULL);

        _d3d11Device = d3d11Device;
        _total = 0;
        _current = 0;
    }

    static std::shared_ptr<vertex_declaration> create(ID3D11Device* device)
    {
        return std::make_shared<vertex_declaration>(device);
    }

    // b::iunknown_provider

    size_t getInterfaceCount()
    {
        return 1;
    }

    WRL::ComPtr<IUnknown> getInterface(size_t index)
    {
        BAJA_CHECK_ARG(index == 0);
        WRL::ComPtr<IUnknown> returnValue;
        _inputLayout.As(&returnValue);
        return returnValue;
    }
    
    // Begins definition of a vertex
    void beginDefinition(const uint32 totalElements)
    {
        BAJA_CHECK_ARG(totalElements > 0);
       
        win32_result wr;

        if (_inputLayout.Get() != NULL) wr = E_FAIL; // ERROR: 
        if (_definition.size() != 0) wr = E_FAIL; // ERROR: 

        // allocate memory for the definition
        _definition.resize(totalElements+1);

        std::for_each(
            _definition.begin(),
            _definition.end(),
            [&](D3D11_INPUT_ELEMENT_DESC& desc)
        {
            D3D11_INPUT_ELEMENT_DESC* p = &desc;
            ZeroMemory(p, sizeof(D3D11_INPUT_ELEMENT_DESC));
        });

        _total = totalElements;
        _current = 0;
    }
    
    // Adds an element to the vertex declaration
    void addElement( 
        const uint32 streamIndex,
        const uint32 byteOffset,
        const vertex_decl_type elementType,
        const vertex_decl_usage elementUsage,
        const uint32 usageIndex)
    {
        win32_result wr;

        if (_inputLayout.Get() != NULL) wr = E_FAIL; // ERROR: 
        if (_definition.size() == 0) wr = E_FAIL; // ERROR: 
        if (_current >= _total) wr = E_FAIL; // ERROR:

        // populate the element
        D3D11_INPUT_ELEMENT_DESC& element = _definition[_current];

        element.SemanticName = d::toSemanticName(elementUsage);
        element.SemanticIndex = usageIndex;
        element.Format = d::toSemanticFormat(elementType);
        element.InputSlot = streamIndex;
        element.AlignedByteOffset = byteOffset;
        element.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
        element.InstanceDataStepRate = 0;

        // advance to next element
        _current++;
    }
    
    // Ends the definition, creating the D3D vertex declaration object
    void endDefinition(const void* shaderBytes, const size_t shaderSize)
    {
        win32_result wr;
        
        if (_inputLayout.Get() != NULL) wr = E_FAIL; // ERROR: 
        if (_definition.size() == 0) wr = E_FAIL; // ERROR: 

        // create the input layout
        wr = _d3d11Device->CreateInputLayout(
            _definition.data(),
            _total,
            shaderBytes,
            shaderSize,
            _inputLayout.GetAddressOf()
            );

        // once successfully created, cleanup memory and release the d3d11 device
        _definition.clear();
        _d3d11Device.Reset();
    }
};

// creates a vertex declaration
inline std::shared_ptr<ivertex_declaration> 
engine::createVertexDeclaration() 
{ 
    std::shared_ptr<vertex_declaration> returnValue = vertex_declaration::create(_d3d11Device.Get());
    return std::dynamic_pointer_cast<ivertex_declaration>(returnValue);
}

}}} // namespace baja.graphics.dx11_1
